import asyncio
import datetime
import os
import pickle
import aiofiles
from scipy import signal
import aiohttp
import numpy as np
from nicegui import ui


from common.third_party.devmem import DevMem
from services.ad936x.api import Ad936xApi, DoubleGain, DoubleAttenuation
from services.server_integration.api import ServerIntegrationApi
from services.automatic_control.api import AutomaticControlApi
from services.automatic_control.ppc import PilotPowerCalculation
from services.services_module.api import ServicesModuleApi
from services.relays_module.api import RelaysModuleApi
from services.telemetry.api import TelemetryApi
from services.control.api import ControlApi
from services.ocb.api import OcbApi
from services.dsp.api import DspApi
from settings import settings


AD_API = Ad936xApi()
SI_API = ServerIntegrationApi()
AC_API = AutomaticControlApi()
SM_API = ServicesModuleApi()
RM_API = RelaysModuleApi()
TEL_API = TelemetryApi()
OCB_API = OcbApi()
CTR_API = ControlApi()
DSP_API = DspApi()
PPC = PilotPowerCalculation()


BLOCK_STATE = CTR_API.get_control_state('block_state')

def add_new_value(path: str, value: float):
    with open(f'{settings.ROOT_PATH}/logs/{path}.txt', 'a') as file:
        file.write(str(value) + '\n')

def read_all_values(path: str) -> list[float]:
    data = []
    with open(f'{settings.ROOT_PATH}/logs/{path}.txt', 'r') as file:
        for line in file:
            data.append(float(line))

    return data

def read_time() -> str:
    time = datetime.datetime.utcnow()
    return time.strftime('%Y-%m-%dT%H:%M:%S')

def read_temperature(index: int) -> float:
    if index in (0, 1):
        return RM_API.get_telemetry(index).temperature
    elif index in (2, 3, 4, 5):
        return SM_API.get_telemetry(index - 2).temperature
    else:
        return TEL_API.get_pwr_telemetry().temperature5v

async def read_input_power(index: int):
    return await AC_API.read_input_power(index)

async def read_output_power(index: int):
    return await AC_API.read_output_power(index)

async def read_pilot_power_rx_a(index: int):
    return await AC_API.read_pilot_power_rx_a(index)

async def read_pilot_power_rx_b(index: int):
    return await AC_API.read_pilot_power_rx_b(index)

async def read_pilot_power_rx_a_nl(index: int):
    return await AC_API.read_pilot_power_rx_a_nl(index)

async def read_pilot_power_rx_b_nl(index: int):
    return await AC_API.read_pilot_power_rx_b_nl(index)

async def read_pilot_power_rx(index: int):
    return await AC_API.read_pilot_power_rx(index)

async def read_pilot_power_rx_nl(index: int):
    return await AC_API.read_pilot_power_rx_nl(index)

async def read_pilot_power_pa_rx(index: int):
    return await AC_API.read_pilot_power_pa_rx(index)

async def read_pilot_power_pa_rx_nl(index: int):
    return await AC_API.read_pilot_power_pa_rx_nl(index)

async def read_pilot_threshold(index: int):
    return await AC_API.read_pilot_threshold(index)

async def read_pilot_power_tx(index: int):
    return await AC_API.read_pilot_power_rx(index)

async def sync_ad(index: int):
    return await AC_API.sync_ad(index)

async def get_actual_reference_freq_40mhz() -> float:
    return float(await AC_API.get_actual_reference_freq_40mhz())

def read_ad_gain(index: int):
    if index == 6:
        config = AD_API.get_config(3)
        return config.gain_0

    ad_index = (0, 0, 1, 1, 2, 2)[index]
    gain_index = (0, 1, 1, 0, 1, 0)[index]

    config = AD_API.get_config(ad_index)
    if ad_index != 0 and CTR_API.get_control_state('block_state'):
        return (config.attenuation_0, config.attenuation_1)[gain_index]
    else:
        return (config.gain_0, config.gain_1)[gain_index]

def read_ad_attenuation(index: int):
    if index == 6:
        config = AD_API.get_config(3)
        return config.attenuation_0

    ad_index = (0, 0, 1, 1, 2, 2)[index]
    attenuation_index = (0, 1, 1, 0, 1, 0)[index]

    config = AD_API.get_config(ad_index)
    if ad_index != 0 and CTR_API.get_control_state('block_state'):
        return (config.gain_0, config.gain_1)[attenuation_index]
    else:
        return (config.attenuation_0, config.attenuation_1)[attenuation_index]

def write_ad_gain(index: int, value: int):
    if index == 6:
        AD_API.set_gain(3, DoubleGain(gain_0=value, gain_1=value))

    ad_index = (0, 0, 1, 1, 2, 2)[index]
    gain_index = (0, 1, 1, 0, 1, 0)[index]

    config = AD_API.get_config(ad_index)
    gains = [config.gain_0, config.gain_1]
    attentions = [config.attenuation_0, config.attenuation_1]

    gains[gain_index] = value
    attentions[gain_index] = value
    if ad_index != 0 and CTR_API.get_control_state('block_state'):
        AD_API.set_attenuation(ad_index, DoubleAttenuation(attenuation_0=attentions[0], attenuation_1=attentions[1]))
    else:
        AD_API.set_gain(ad_index, DoubleGain(gain_0=gains[0], gain_1=gains[1]))

def write_ad_attenuation(index: int, value: int):
    if index == 6:
        AD_API.set_attenuation(3, DoubleAttenuation(attenuation_0=value, attenuation_1=value))

    ad_index = (0, 0, 1, 1, 2, 2)[index]
    attenuation_index = (0, 1, 1, 0, 1, 0)[index]

    config = AD_API.get_config(ad_index)
    gains = [config.gain_0, config.gain_1]
    attentions = [config.attenuation_0, config.attenuation_1]

    gains[attenuation_index] = value
    attentions[attenuation_index] = value
    if ad_index != 0 and CTR_API.get_control_state('block_state'):
        AD_API.set_gain(ad_index, DoubleGain(gain_0=gains[0], gain_1=gains[1]))
    else:
        AD_API.set_attenuation(ad_index, DoubleAttenuation(attenuation_0=attentions[0], attenuation_1=attentions[1]))

def set_pilot_width(index: int, width: int):
    base_address = [0x83E0_0000, 0x83E1_0000, 0x83E5_0000][index]
    address = base_address + 4 * 4

    width_code = int((width * 60_000_000) / 1_000_000 - 2)
    mem = DevMem(address=address)
    mem.write(value=width_code)

def get_pilot_width(index: int) -> int:
    base_address = [0x83E0_0000, 0x83E1_0000, 0x83E5_0000][index]
    address = base_address + 4 * 4

    mem = DevMem(address=address)
    width_code = mem.read().get_value()
    return int((width_code + 2) * 1_000_000 / 60_000_000)


def read_current(index: int) -> float:
    name = (
        'link_0_ocb', 'link_1_ocb',
        'service_0_ocb', 'service_1_ocb',
        'service_2_ocb', 'service_3_ocb'
    )[index]

    return OCB_API.get_all_ocb_current_28v().get(name)


def set_thresh(values: list[int]):
    def read(address: int, signed=False) -> int:
        mem = DevMem(address=address)
        return mem.get_value()

    def write(address: int, value: int):
        mem = DevMem(address=address)
        mem.write(value=value)

    base_address = [0x83E0_0000, 0x83E1_0000, 0x83E5_0000]
    for idx, base_address in enumerate(base_address):
        i = 5
        address = base_address + 4 * i
        rdata = read(address, True)
        rdata = rdata & 4293001215
        data = rdata + values[idx] * 131072
        write(address, data)


def read_ra_clocks_measured() -> float | None:
    ra_clocks_base_addr = 0x43C1_0000
    mem = DevMem(address=ra_clocks_base_addr, number_of_words=40)

    av0, av1, av2, av3 = mem.read(0x0).get_value(), mem.read(0x10).get_value(), mem.read(0x20).get_value(), mem.read(0x20).get_value()
    ra_clocks_measured = 0
    if av0:
        lo = mem.read(0x8).get_value()
        hi = mem.read(0xC).get_value()
        hi = hi << 32
        ra_clocks_measured = (hi + lo) / 10 - 1
    if av1:
        lo = mem.read(0x18).get_value()
        hi = mem.read(0x1C).get_value()
        hi = hi << 32
        ra_clocks_measured = (hi + lo) / 100 - 1
    # if av2:
    #     lo = mem.read(0x28).get_value()
    #     hi = mem.read(0x2C).get_value()
    #     hi = hi << 32
    #     ra_clocks_measured = (hi + lo) / 500 - 1
    # if av2:
    #     lo = mem.read(0x38).get_value()
    #     hi = mem.read(0x3C).get_value()
    #     hi = hi << 32
    #     ra_clocks_measured = (hi + lo) / 1000 - 1
    else:
        return None

    return ra_clocks_measured

_read_ra_clocks_measured_delta = 0.0
def read_ra_clocks_measured_delta():
    global _read_ra_clocks_measured_delta

    clocks_measured = read_ra_clocks_measured()
    if clocks_measured is not None:
        _read_ra_clocks_measured_delta = 40_000_000 - clocks_measured

    return _read_ra_clocks_measured_delta


def services_pilot_set(index: int, state: bool):
    bin_index = (1, 8, 3, 4)[index]
    DevMem(address=0x83C1_0000)\
        .read(0)[bin_index] = state

async def read_sm_pap(sm_index: int):
    async def update_service_tx_waveforms(index: int):
        s = [0, 0, 0, 0]
        s[index] = 1
        try:
            uri = f'http://localhost:3030/update-raw-services-out-power-waveforms/{s[0]}/{s[1]}/{s[2]}/{s[3]}'
            async with aiohttp.request('GET', uri) as response:
                if response.status == 200:
                    body = await response.text()
                    papdma = list(map(float, body.split(' ')))
                    return True, papdma
        except Exception:
            return False, [0, 0, 0, 0]

    # def read_service_waveforms():
    #     base_path = f'{settings.ROOT_PATH}//environment/dma'
    #     out_waveform = np.fromfile(f'{base_path}/raw_out_power_meters_{sm_index}', dtype=np.int16)
    #     out_waveform = out_waveform[0::2] + 1j * out_waveform[1::2]
    #     return out_waveform[:1024], out_waveform[1024:2048], out_waveform[2048:]
    #
    # def clac_ppc(out_waveform, service_index: int):
    #     config = SI_API.get_full_configuration()
    #
    #     bw = config.service_channels[service_index].bandwidth * 1_000_000
    #     if service_index in [2, 3]:
    #         if config.unit_configuration.channel_3_and_4_config.value == 'GSM':
    #             bw = 10_000_000
    #         elif config.unit_configuration.channel_3_and_4_config.value == 'LTE_GSM':
    #             bw += 10_000_000
    #
    #     rounder = 60_000_000 / 1024
    #     pilot_freq = -int((bw // 2 + 2_000_000) // rounder * rounder)
    #     return PPC.calc(out_waveform, 60_000_000, pilot_freq)
    #
    status, pap_dma = await update_service_tx_waveforms(sm_index)
    # out_waveform = read_service_waveforms()

    return pap_dma

async def read_sm_all_power(sm_index: int):
    Fs = 60_000_000  # sample rate
    Ts = 1 / Fs

    rounder = 60_000_000 / 1024
    config = SI_API.get_full_configuration()
    bw = config.service_channels[sm_index].bandwidth * 1_000_000
    pilot_freq = -int((bw // 2 + 2_000_000) // rounder * rounder)

    def read_service_waveforms():
        base_path = f'{settings.ROOT_PATH}//environment/dma'
        out_waveform = np.fromfile(f'{base_path}/raw_out_power_meters_{sm_index}', dtype=np.int16)
        out_waveform = out_waveform[0::2] + 1j * out_waveform[1::2]
        return out_waveform[:1024], out_waveform[1024:2048], out_waveform[2048:]

    def psd(data):
        n = len(data)

        psd = np.abs(np.fft.fft(data)) ** 2 / (n * Fs)
        psd_log = 10.0 * np.log10(psd)
        return np.fft.fftshift(psd_log)

    def calc_a(data, freq=-9_500_000):
        data = psd(data)
        n = len(data)
        scale = Fs // n
        delta = 400_000 // scale

        mi = (30_000_000 + freq) // scale
        si = mi - delta
        ei = mi + delta

        return np.max(data[si:ei])

    def calc_b(data, freq=-9_500_000, fs=60_000_000):
        n = len(data)
        ts = 1 / fs
        h = signal.firwin(n, (1, 1e6), pass_zero=False, fs=fs)
        shift = np.exp(2j * np.pi * -freq * np.arange(0.0, ts * n, ts))
        filtered = signal.convolve(h, data * shift)
        data_psd = psd(filtered)

        return np.max(data_psd)

    def calc_c(data, freq=-9_500_000, fs=60_000_000):
        n = len(data)
        ts = 1 / fs
        h = signal.firwin(n, (1, 5), pass_zero=False, fs=fs)
        shift = np.exp(2j * np.pi * -freq * np.arange(0.0, ts * n, ts))
        filtered = signal.convolve(h, data * shift)
        filtered_decimated = signal.decimate(filtered, 300)
        filtered_decimated_log = 10.0 * np.log10(np.abs(filtered_decimated) ** 2)

        return np.max(filtered_decimated_log)

    papdma = await read_sm_pap(sm_index)
    # data = read_service_waveforms()
    # pap_a = [
    #     calc_a(data[0], pilot_freq),
    #     calc_a(data[1], pilot_freq),
    #     calc_a(data[2], pilot_freq)
    # ]
    # pap_b = [
    #     calc_b(data[0], pilot_freq),
    #     calc_b(data[1], pilot_freq),
    #     calc_b(data[2], pilot_freq)
    # ]
    # pap_c = [
    #     calc_c(data[0], pilot_freq),
    #     calc_c(data[1], pilot_freq),
    #     calc_c(data[2], pilot_freq)
    # ]

    return float(papdma[sm_index])


AD_LOCk = asyncio.Lock()

class RmUI(ui.element):
    __slots__ = ['_grid', '_index', '_auto_update_timer',
                 '_rx_gain_input', '_tx_gain_input', '_pilot_width_input', '_pilot_cycles', '_rx_thresh',
                 '_f_rx_gain_input', '_f_tx_gain_input', '_tx_thresh', '_f_pilot_width_input', '_auto_sync_ad']

    def __init__(self, index: int):
        super().__init__('div')
        self._index = index
        self._auto_update_timer = ui.timer(66.6, self.update_grid, active=False)

        with ui.row():
            ui.button('Update', on_click=self.update_grid)
            ui.switch('Auto Update').bind_value(self._auto_update_timer, 'active')
            ui.button('Clear', on_click=self.clear)
            ui.button('Export', on_click=lambda: self._grid.call_api_method('exportDataAsCsv'))

        with ui.row():
            ui.switch('DMA', value=True, on_change=self.on_off_dma)
            ui.switch('CFC', value=True, on_change=self.on_off_cfc)
            self._auto_sync_ad = ui.switch('Auto Sync Ad', value=True)
            ui.button('Sync Ad', on_click=self.sync_ad)

        with ui.row():
            ui.button("Update", on_click=self.update_rx_tx_gain_width)
            self._rx_gain_input = ui.number('Rx Gain', value=read_ad_gain(self._index), on_change=self.set_gain_rx, format='%.0f')
            self._tx_gain_input = ui.number('Tx Gain', value=70 - read_ad_attenuation(self._index), on_change=self.set_gain_tx, format='%.0f')
            self._pilot_width_input = ui.number('Rx Pilot Width', value=get_pilot_width(self._index), on_change=self.set_pilot_width, format='%.0f')
            self._rx_thresh = ui.number('Thresh', value=2, on_change=self.set_thresh,format='%.0f')
            self._pilot_cycles = ui.number('Cycles', value=DSP_API.get_pilots_cycles(), on_change=self.set_cycles, format='%.0f')

        with ui.row():
            ui.button("Update", on_click=self.update_feedback)
            self._f_rx_gain_input = ui.number('F Rx Gain', value=read_ad_gain(6), on_change=self.set_f_gain_rx, format='%.0f')
            self._f_tx_gain_input = ui.number('F Tx Gain', value=70 - read_ad_attenuation(6), on_change=self.set_f_gain_tx, format='%.0f')
            self._f_pilot_width_input = ui.number('Tx Pilot Width', value=get_pilot_width(2), on_change=self.set_f_pilot_width, format='%.0f')
            self._tx_thresh = ui.number('Thresh', value=2, on_change=self.set_thresh_f, format='%.0f')


        self._grid = ui.aggrid({
            'defaultColDef': {'flex': 1},
            'columnDefs': [
                {'headerName': 'Time', 'field': 'time', 'filter': 'agTextColumnFilter', 'floatingFilter': True, 'sortable': True},
                {'headerName': 'PTemp', 'field': 'ptemp', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                {'headerName': 'Temp', 'field': 'temp', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                {'headerName': 'Gain', 'field': 'gain', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'Atte', 'field': 'atte', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},

                {'headerName': 'RxTH', 'field': 'rxth', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                # {'headerName': 'RxPA', 'field': 'rxpa', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'RxPB', 'field': 'rxpb', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'RxPANL', 'field': 'rxpanl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'RxPBNL', 'field': 'rxpbnl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                {'headerName': 'RxPP', 'field': 'rxpp', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'RxPPNL', 'field': 'rxppnl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                {'headerName': 'RxPPA', 'field': 'rxppa', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'RxPPANL', 'field': 'rxppanl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                {'headerName': 'RxPPM', 'field': 'rxppm', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                {'headerName': 'TxTH', 'field': 'txth', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'TxPA', 'field': 'txpa', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'TxPB', 'field': 'txpb', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'TxPANL', 'field': 'txpanl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'TxPBNL', 'field': 'txpbnl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                {'headerName': 'TxPP', 'field': 'txpp', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'TxPPNL', 'field': 'txppnl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                {'headerName': 'TxPPA', 'field': 'txppa', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'TxPPANL', 'field': 'txppanl', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                {'headerName': 'TxPPM', 'field': 'txppm', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                {'headerName': 'Delta', 'field': 'delta', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                {'headerName': 'RDelta', 'field': 'rdelta', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                # {'headerName': 'TxPP', 'field': 'txpp', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                # {'headerName': 'IPow', 'field': 'ipow', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'OPow', 'field': 'opow', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},
                # {'headerName': 'Curr', 'field': 'curr', 'filter': 'agNumberColumnFilter', 'floatingFilter': True, 'sortable': True},

                {'headerName': 'RxPLS', 'field': 'rxpls'},
                {'headerName': 'TxPLS', 'field': 'txpls'},
            ],
            'rowData': self._load_row_data(),
            'rowSelection': 'multiple',
        }).classes('max-h-2000')

    async def sync_ad(self):
        await sync_ad(self._index)

    def set_gain_rx(self, gain):
        write_ad_gain(self._index, int(gain.value))

    def set_gain_tx(self, gain):
        write_ad_attenuation(self._index, 70 - int(gain.value))

    def set_f_gain_rx(self, gain):
        write_ad_gain(6, int(gain.value))

    def set_f_gain_tx(self, gain):
        write_ad_attenuation(6, 70 - int(gain.value))

    def set_pilot_width(self, width):
        for idx in range(2):
            set_pilot_width(idx, int(width.value))

    def set_f_pilot_width(self, width):
        set_pilot_width(2, int(width.value))

    def set_thresh(self, threshold):
        set_thresh([int(threshold.value), int(threshold.value), int(self._tx_thresh.value)])

    def set_thresh_f(self, threshold):
        set_thresh([int(self._rx_thresh.value), int(self._rx_thresh.value), int(threshold.value)])

    def set_cycles(self, cycles):
        DSP_API.set_pilots_cycles(int(cycles.value))

    def update_rx_tx_gain_width(self):
        self._rx_gain_input.value = read_ad_gain(self._index)
        self._tx_gain_input.value = 70 - read_ad_attenuation(self._index)
        self._pilot_width_input.value = get_pilot_width(self._index)
        self._pilot_cycles.value = DSP_API.get_pilots_cycles()

    def update_feedback(self):
        self._f_rx_gain_input.value = read_ad_gain(6)
        self._f_tx_gain_input.value = 70 - read_ad_attenuation(6)
        self._f_pilot_width_input.value = get_pilot_width(2)

    async def on_off_cfc(self, state):
        await AC_API.carrier_frequency_control_enable(state.value)

    def on_off_dma(self, state):
        if state.value:
            os.system('systemctl start rcs-dma.service')
        else:
            os.system('systemctl stop rcs-dma.service')

    def clear(self):
        try:
            filename = f'{settings.ROOT_PATH}/data/rm-{self._index}.pickle'
            os.remove(filename)
            self._grid.options['rowData'] = []
            self._grid.update()
        except:
            pass

    async def update_grid(self):
        async with AD_LOCk:
            if self._auto_sync_ad.value:
                await self.sync_ad()
                await asyncio.sleep(1.2)

            read_index = self._index
            self._grid.options['rowData'].append({
                'time': read_time(),
                'ptemp': round(read_temperature(-1), 2),
                'temp': round(read_temperature(read_index), 2),
                'gain': round(read_ad_gain(read_index), 2),
                'atte': round(read_ad_attenuation(read_index), 2),

                'rxth': await read_pilot_threshold(read_index),

                # 'rxpa': round(await read_pilot_power_rx_a(read_index), 2),
                # 'rxpb': round(await read_pilot_power_rx_b(read_index), 2),
                # 'rxpanl': await read_pilot_power_rx_a_nl(read_index),
                # 'rxpbnl': await read_pilot_power_rx_b_nl(read_index),

                'rxpp': round(await read_pilot_power_rx(read_index), 2),
                # 'rxppnl': await read_pilot_power_rx_nl(read_index),

                'rxppa': round(await read_pilot_power_pa_rx(read_index), 2),
                # 'rxppanl': await read_pilot_power_pa_rx_nl(read_index),

                'rxppm': max(round(await read_pilot_power_rx(read_index), 2), round(await read_pilot_power_pa_rx(read_index), 2)),

                # 'txpa': round(await read_pilot_power_rx_a(2), 2),
                # 'txpb': round(await read_pilot_power_rx_b(2), 2),
                # 'txpanl': await read_pilot_power_rx_a_nl(2),
                # 'txpbnl': await read_pilot_power_rx_b_nl(2),

                'txth': await read_pilot_threshold(2),

                'txpp': round(await read_pilot_power_rx(2), 2),
                # 'txppnl': await read_pilot_power_rx_nl(2),

                'txppa': round(await read_pilot_power_pa_rx(2), 2),
                # 'txppanl': await read_pilot_power_pa_rx_nl(2),

                'txppm': max(round(await read_pilot_power_rx(2), 2), round(await read_pilot_power_pa_rx(2), 2)),

                'delta': round(40_000_000 - await get_actual_reference_freq_40mhz(), 6),
                'rdelta': round(read_ra_clocks_measured_delta(), 6),

                # 'txpp': round(await read_pilot_power_tx(read_index), 2),
                # 'ipow': round(await read_input_power(read_index), 2),
                # 'opow': round(await read_output_power(read_index), 2),
                # 'curr': round(read_current(read_index), 2),
                #
                'rxpls': await AC_API.read_status_of_link(read_index),
                'txpls': await AC_API.read_status_of_link(2)
            })
        self._grid.update()
        self._save_row_data()

    def _save_row_data(self):
        filename = f'{settings.ROOT_PATH}/data/rm-{self._index}.pickle'
        os.makedirs(os.path.dirname(filename), exist_ok=True)
        with open(filename, 'wb') as file:
            pickle.dump(self._grid.options['rowData'], file=file)

    def _load_row_data(self):
        try:
            with open(f'{settings.ROOT_PATH}/data/rm-{self._index}.pickle', 'rb') as file:
                return pickle.load(file)
        except Exception:
            return []


class SmUI(ui.element):
    __slots__ = ['_grid', '_index', '_auto_update_timer', '_forced_pilot_state', '_forced_pilot_timer']

    def __init__(self, index: int):
        super().__init__('div')
        self._index = index
        self._auto_update_timer = ui.timer(77, self.update, active=False)

        self._forced_pilot_state = None
        self._forced_pilot_timer = ui.timer(0.15, self.set_pilot_event, active=True)

        with ui.row():
            ui.button('Update', on_click=self.update)
            ui.switch('Auto Update').bind_value(self._auto_update_timer, 'active')
            ui.toggle(
                {
                    None: 'No Forced Pilot',
                    False: 'Forced Pilot Off',
                    True: 'Forced Pilot On'
                },
                on_change=lambda: services_pilot_set(self._index, False)
            ).bind_value(self, '_forced_pilot_state')
            ui.button('Sync Ad', on_click=self.sync_ad)
            ui.button('Clear', on_click=self.clear)
            ui.button('Export', on_click=lambda: self._grid.call_api_method('exportDataAsCsv'))

        self._grid = ui.aggrid({
            'defaultColDef': {'flex': 1},
            'columnDefs': [
                {'headerName': 'Time', 'field': 'time', 'filter': 'agTextColumnFilter', 'floatingFilter': True},
                {'headerName': 'PTemp', 'field': 'ptemp', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'Temp', 'field': 'temp', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'Gain', 'field': 'gain', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'Atte', 'field': 'atte', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                # {'headerName': 'PAP', 'field': 'pap', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                # {'headerName': 'PAP-A', 'field': 'papa', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                # {'headerName': 'PAP-B', 'field': 'papb', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                # {'headerName': 'PAP-C', 'field': 'papc', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                # {'headerName': 'PAP-D', 'field': 'papd', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'PAP-DMA', 'field': 'papdma', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'IPow', 'field': 'ipow', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'OPow', 'field': 'opow', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                # {'headerName': 'Pow', 'field': 'pow', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
                {'headerName': 'Curr', 'field': 'curr', 'filter': 'agNumberColumnFilter', 'floatingFilter': True},
            ],
            'rowData': self._load_row_data(),
            'rowSelection': 'multiple',
            'domLayout': 'print'
        }).classes('max-h-500')

    async def sync_ad(self):
        await sync_ad(self._index + 2)

    async def set_pilot_event(self):
        if self._forced_pilot_state is None:
            return

        services_pilot_set(self._index, self._forced_pilot_state)

    def clear(self):
        try:
            filename = f'{settings.ROOT_PATH}/data/sm-{self._index}.pickle'
            os.remove(filename)
            self._grid.options['rowData'] = []
            self._grid.update()
        except:
            pass

    async def update(self):
        read_index = self._index + 2
        papdma = await read_sm_all_power(self._index)
        self._grid.options['rowData'].append({
            'time': read_time(),
            'ptemp': round(read_temperature(-1), 2),
            'temp': round(read_temperature(read_index), 2),
            'gain': round(read_ad_gain(read_index), 2),
            'atte': round(read_ad_attenuation(read_index), 2),
            # 'pap': round(pap, 2),
            # 'papa': round(pap_a, 2),
            # 'papb': round(pap_b, 2),
            # 'papc': round(pap_c, 2),
            # 'papd': round(pap_d, 2),
            'papdma': round(papdma, 2),
            'ipow': round(await read_input_power(read_index), 2),
            'opow': round(await read_output_power(read_index), 2),
            # 'pow': round((await AC_API.get_pilot_telemetry()).base_station_current_power[read_index], 2),
            'curr': round(read_current(read_index), 2),
        })
        self._grid.update()
        self._save_row_data()

    def _save_row_data(self):
        filename = f'{settings.ROOT_PATH}/data/sm-{self._index}.pickle'
        os.makedirs(os.path.dirname(filename), exist_ok=True)
        with open(filename, 'wb') as file:
            pickle.dump(self._grid.options['rowData'], file=file)

    def _load_row_data(self):
        try:
            with open(f'{settings.ROOT_PATH}/data/sm-{self._index}.pickle', 'rb') as file:
                return pickle.load(file)
        except Exception:
            return []


with ui.tabs().classes('w-full') as tabs:
    sm_tabs = [
        ui.tab('RM0'),
        ui.tab('RM1'),
        ui.tab('SM0'),
        ui.tab('SM1'),
        ui.tab('SM2'),
        ui.tab('SM3')
    ]
with ui.tab_panels(tabs, value='SM0').classes('w-full'):
    for i in range(6):
        if i in (0, 1):
            with ui.tab_panel(f'RM{i}').classes('w-full'):
                RmUI(i).classes('w-full')
        else:
            with ui.tab_panel(f'SM{i-2}').classes('w-full'):
                SmUI(i-2)

ui.run(port=8800, title='debug', reload=False, loop="asyncio")